<?php
/**
  *
  * Copyright (c) 2009, Persistent Systems Limited
  *
  * Redistribution and use, with or without modification, are permitted 
  *  provided that the following  conditions are met:
  *   - Redistributions of source code must retain the above copyright notice, 
  *     this list of conditions and the following disclaimer.
  *   - Neither the name of Persistent Systems Limited nor the names of its contributors 
  *     may be used to endorse or promote products derived from this software 
  *     without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
  
/**
  *
  * This file contains the definition of AtomParser class.
  */

class AtomParser
{
    protected $objectContext;
    protected $domDocument;
    protected static $namespaces = array(
                                    'default' => 'http://www.w3.org/2005/Atom',
                                    'd' => 'http://schemas.microsoft.com/ado/2007/08/dataservices',
                                    'm' => 'http://schemas.microsoft.com/ado/2007/08/dataservices/metadata'
                                 );
    protected static $QUERY_ROOT_FEED = '/default:feed';
    protected static $QUERY_ROOT_ENTRY = '/default:entry';
    protected static $QUERY_TITLE = 'default:title';
    protected static $QUERY_ENTRY = 'default:entry';
    protected static $QUERY_ENTRY_ID = '/default:entry/default:id';
    protected static $QUERY_ID = 'default:id';
    protected static $QUERY_ENTRY_EDIT_LINK = '/default:entry/default:link[@rel="edit"]';
    protected static $QUERY_EDIT_LINK = 'default:link[@rel="edit"]';
    protected static $QUERY_NON_EDIT_LINK = 'default:link[@rel!="edit"]';
    protected static $QUERY_INLINE_FEED = 'm:inline/default:feed';
    protected static $QUERY_ENTRY_PROPERTIES = '/default:entry/default:content/m:properties/d:*';
    protected static $QUERY_PROPERTIES = 'default:content/m:properties/d:*';
    protected static $QUERY_ERROR_MESSAGE = '/default:error/default:message';
    protected static $QUERY_INNER_EXCEPTION = '/default:error/default:innererror/default:internalexception/default:message';
    
    protected static $ERROR_TAG = "<error xmlns=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\">";
    
    public function AtomParser($xml, $objectContext)
    {
        $this->domDocument = new DOMDocument();
        $this->domDocument->loadXML( $xml );
        $this->objectContext = $objectContext;
    }
    
    public function EnumerateObjects()
    {
        $result = array();
        $xPath = new DOMXPath($this->domDocument);
        self::ApplyNamespace($xPath);
        $feeds = $xPath->query(self::$QUERY_ROOT_FEED);
        
        if (!$feeds->length)
        {
            $entries = $xPath->query(self::$QUERY_ROOT_ENTRY);
            if (!$entries->length)
            {
                throw new Exception("Invalid XML");
            }
            
            $result[] = self::EnumerateEntry( $entries->item(0) );
        }
        else
        {
            $result =  self::EnumerateFeed($feeds->item(0), $feedType);
        }
        
        return $result;
    }
    
    protected function EnumerateFeed($feed, &$feedType, $parentObject = null)
    {        
        $entryCollection = array();
        $xPath = self::GetXPathForNode($feed);
        
        $titles = $xPath->query(self::$QUERY_TITLE);
        $feedType = $titles->item(0)->nodeValue;
        
        $entries = $xPath->query(self::$QUERY_ENTRY);
        foreach ($entries as $entry)
        {
            $entryCollection[] = $this->EnumerateEntry($entry, $parentObject);
        }
        
        return $entryCollection;
    }
    
    protected function EnumerateEntry($entry, $parentObject = null)
    {
        $xPath = self::GetXPathForNode($entry);
        
        $ids = $xPath->query(self::$QUERY_ID);
        $uri = $ids->item(0)->nodeValue;
        
        $entitySet = Utility::GetEntitySetFromUrl($uri);
        $entityType = $this->objectContext->GetEntityTypeNameFromSet($entitySet);
        $object = $this->objectContext->AddToObjectToResource($entityType, $uri);
        
        if ($parentObject != null)
        {
            $this->objectContext->AddToBindings($parentObject, $entitySet, $object);
        }
        
        $links = $xPath->query(self::$QUERY_NON_EDIT_LINK);
        foreach ($links as $link)
        {
            self::EnumerateLink($link, $object);
        }
        
        $relLinks = self::GetRelatedLinks($links);
        $class = new ReflectionClass(get_class($object));
        $method = $class->getMethod('setRelatedLinks');
        $method->invoke($object,$relLinks);
        
        $properties = $xPath->query(self::$QUERY_PROPERTIES);
        foreach ($properties as $property)
        {
            self::SetObjectProperty($object, $property);
        }
        
        return $object;
    }
    
    protected function EnumerateLink($link, $object)
    {
        $xPath = self::GetXPathForNode($link);
        $feeds = $xPath->query(self::$QUERY_INLINE_FEED);
        
        foreach ($feeds as $feed)
        {
            $entryCollection = $this->EnumerateFeed($feed, $feedType, $object);
            $property = new ReflectionProperty($object, $feedType);
            $property->setValue($object, $entryCollection);
        }
        
    }
    
    protected function GetRelatedLinks($links)
    {
        $relLinks = array();
        foreach ($links as $link)
        {
            $feedNode = $link->getElementsByTagNameNS(self::$namespaces['default'], 'feed');
            if ($feedNode->item(0) === NULL)
            {
                $relUri =  self::GetAttribute($link, "href");
                $index = Utility::reverseFind($relUri, '/');
                $entityName = substr($relUri, $index + 1, strlen($relUri) - $index);
                $relLinks[$entityName] = $relUri;
            }
        }
        return $relLinks;
    }
    
    protected function SetObjectProperty($object, $property)
    {
        $prefix = $property->prefix;
        $name = $property->nodeName;
        
        if ($prefix != "default")
        {
            $prefix = $prefix . ":";
            $pos =  ( ( $index = strpos($name,$prefix)) === FALSE) ? 0 : $index + strlen($prefix);
            $name = substr($name, $pos);
        }
        
        $value = $property->nodeValue;
        try
        {
            $property = new ReflectionProperty($object, $name);
        
            //Do Atom format to PHP format conversion if required for property value ex:
            //if (strpos($property->getDocComment(), 'Edm.DateTime') == TRUE) 
            //{
            //    $value = AtomDateToPHPDate()
            //}
        
            $property->setValue($object, $value);
        }
        catch (ReflectionException $ex)
        {
            // Ignore the error at the moment. TBD later.
        }        
    }
    
    protected function GetXPathForNode($node)
    {
        $domDocument = self::GetDomDocumentFromNode( $node );
        $xPath = new DOMXPath($domDocument);
        self::ApplyNamespace($xPath);
        return $xPath;
    }
    
    protected function GetDomDocumentFromNode($node)
    {
        $domDocument_From_Node = new DomDocument();
        $domNode = $domDocument_From_Node->importNode($node, true);
        $domDocument_From_Node->appendChild($domNode);
        return $domDocument_From_Node;
    }
    
    protected function GetAttribute($node, $attributeName)
    {
        $attributes = $node->attributes;
        foreach ($attributes as $attribute)
        {
            if ($attribute->nodeName == $attributeName)
            {
                return $attribute->value;
            }
        }
        return "";
    }
    
    protected static function ApplyNamespace($xPath)
    {
        foreach (self::$namespaces as $prefix => $namespaceURI)
        {
            $xPath->registerNamespace($prefix, $namespaceURI);
        }
    }
    
    public static function PopulateObject($atomXML, $object, &$uri)
    {
        $domDocument = new DomDocument();
        $domDocument->loadXML( $atomXML );
        $xPath = new DOMXPath($domDocument);
        self::ApplyNamespace($xPath);
        $ids = $xPath->query(self::$QUERY_ENTRY_ID);
        $uri = $ids->item(0)->nodeValue;
        
        $properties = $xPath->query(self::$QUERY_ENTRY_PROPERTIES);
        foreach ($properties as $property)
        {
            self::SetObjectProperty($object, $property);
        }
    }
    
    public static function GetErrorDetails($errorXML, &$outerError, &$innnerError)
    {        
        if (strstr($errorXML, self::$ERROR_TAG) === FALSE)
        {
            $innerError = "";
            $outerError = $errorXML;
        }
        else
        {
            $errorXML = str_replace("innererror xmlns=\"xmlns\"", "innererror", $errorXML);
            $domDocument = new DOMDocument();
            $domDocument->loadXML( $errorXML );
        
            $xPath = new DOMXPath($domDocument);
            $xPath->registerNamespace('default', self::$namespaces['m']);
        
            $outerErrors = $xPath->query(self::$QUERY_ERROR_MESSAGE);
            if ($outerErrors->length)
            {
                $outerError =  $outerErrors->item(0)->nodeValue;
            }
        
            $innerErrors = $xPath->query(self::$QUERY_INNER_EXCEPTION);
            if ($innerErrors->length)
            {
                $innnerError =  $innerErrors->item(0)->nodeValue;
            }
        }
    }
}
?>